﻿using System;

namespace _19_面向对象___值类型_引用类型
{
    class Program
    {
        static void Main(string[] args)
        {

            // 测试 ref 
            Console.WriteLine("------- 方法参数没有 ref -------");
            int age = 15;
            Console.WriteLine($"{nameof(Main)} - 之前 age = {age}");
            Common(age);
            Console.WriteLine($"{nameof(Main)} - 之后 age = {age}");
            /* 
            输出：
            Main - 之前 age = 15
            TestRef_Cankao - age = 45
            Main - 之后 age = 15
            结论： age 内外隔离，互相不影响
             */
            Console.WriteLine("\n------- 方法参数有 ref -------");
            age = 20;
            Console.WriteLine($"{nameof(Main)} - 之前 age = {age}");
            // 带 ref 的参数，传递数据时要把 ref 关键字也写进去
            TestRef(ref age);
            Console.WriteLine($"{nameof(Main)} - 之后 age = {age}");

            /*
              输出：
                Main - 之前 age = 20
                TestRef - age = 50
                Main - 之后 age = 50
             结论： 
              带 ref 的参数，方法内的修改会导致 方法外部的变化
              就是将值类型以引用类型的方式来使用
             */
            Console.WriteLine("------- 方法参数有 ref  测试结束 -------");

            Console.WriteLine("\n------- 方法参数为引用类型 -  string  -------");
            string name = "张三";
            Console.WriteLine($"{nameof(Main)} - 之前 name = {name}");
            TestString(name);
            Console.WriteLine($"{nameof(Main)} - 之后 name = {name}");

            /*
             输出：
            Main - 之前 name = 张三
            TestString - name = 张三 wow~
            Main - 之后 name = 张三
            结论：
            string 方法前后保持不变。
            string 是引用类型，但是内外隔绝，以值类型的方式传参数。
            方法中的参数会尽量的以值类型传递。
            如果想要影响到外面，加 ref 
             */
            Console.WriteLine("\n------- 方法参数为引用类型 -  class  -------");

            Person a = new Person { sex = "女"};
            Console.WriteLine($"{nameof(Main)} - 之前 性别 = {a.sex}");
            TestClass(a);
            Console.WriteLine($"{nameof(Main)} - 之后 性别 = {a.sex}");
            /*
             输出：
            Main - 之前 性别 = 女
            TestClass - 性别 = 未知
            Main - 之后 性别 = 未知 
            结论：
            跟 string 这种引用类型不一样：方法内的修改会影响到方法外部，但是 string 方法外不会被影响。
            把 string 的这种不同记住。
            加上 ref 可以告诉方法：你不用执着的把参数以值类型传递

            得到了一个约定：
            引用类型（非string）一定是会因为方法内的修改而改变。
             
            本质是为什么？
            因为 小 a 是引用类型，参数传递的是一个地址而已，方法内操作的是 小 a 的真实数据，而真实数据只有一份。
             */
            // 测试 out 
            Console.WriteLine("\n------- 方法参数没有 out -------");
            age = 18;
            Console.WriteLine($"{nameof(Main)} - 之前 age = {age}");
            Common(age);
            Console.WriteLine($"{nameof(Main)} - 之后 age = {age}");

            Console.WriteLine("\n------- 方法参数有 out -------");
            age = 16;
            Console.WriteLine($"{nameof(Main)} - 之前 age = {age}");
            TestOut(out age);
            Console.WriteLine($"{nameof(Main)} - 之后 age = {age}");

            /*
             输出：
------- 方法参数没有 out -------
Main - 之前 age = 18
Common - age = 54
Main - 之后 age = 18

------- 方法参数有 out -------
Main - 之前 age = 16
TestOut - age = 100
Main - 之后 age = 100

            结论：
            1. out 和 ref 都会把参数当成引用类型的形式传递，导致方法内对参数的修改会改变传值。
            2. out 为啥存在

             */
            // ref out 区别
            // 共同点：外部有变量，使用 ref out 都会改变这个变量
            int agev2 ;
            TestOut(out agev2);
            TestRef(ref agev2);

            // 区别：out 可以不声明外部的变量 ，但是方法内的变更还是能影响到方法的外部，即便你是值类型
           // 参数的影响的方向：有里到外 （里：里面，方法体里面，外：方法体外）
            TestOut(out int agev3);
            Console.WriteLine( agev3); //外面没声明，方法由内至外（out）推送给你

            // ref 不能由内至外，他是由外至内 ：外部必须先存在 （先声明）
            //TestRef(ref int agev4);  // 这样写 IDE 不认


            TestReference();



        }
        //值类型：int ，long、float 、decimal、char、bool,struct 、 enum  (无符号的省掉了)
        //引用类型：string 、集合（Array 、List）、class 、interface(接口)

        // 值类型：只需要一段单独的内存，数据存放在 栈 中，随用随取随丢弃。

        // 引用类型：需要2段内存，一段用于存真实的数据，一段存取数据的地址
        // 一栋楼有很多房间，每个房间可以存东西，我要访问这些东西，怎么处理？
        // 1. 拿到存放的房间信息：几栋几层几号房 12-45-4502 (这个 ：12-45-4502 就是地址)
        // 2.  通过给我的地址，去存、取东西。
        // 3 . "存储真实数据的内存" =  房间 ，“存取数据的地址” = 房间号 ，内存 = 一栋楼  , 存房间号的是一个表单、 贴墙上、放电脑上
        // 引用类型存取数据类似。

        //ref =  reference  ： 引用
        //out : 出来

        // 一个参考组
        // common 常规、通用的方法的写法
        static void Common(int age)
        {
            age *= 3;
            Console.WriteLine($"{nameof(Common)} - age = {age}");
        }
        static void TestRef(ref int age)
        {
            age = 50;
            Console.WriteLine($"{nameof(TestRef)} - age = {age}");
        }

        static void TestString( string name) 
        {
            name += " wow~";
            Console.WriteLine($"{nameof(TestString)} - name = {name}");
        }

        static void TestClass(Person person) 
        {
            person.sex = "未知";
            Console.WriteLine($"{nameof(TestClass)} - 性别 = {person.sex}");
        }

        static void TestOut(out int age) 
        {
            age = 100;
            Console.WriteLine($"{nameof(TestOut)} - age = {age}");
        }

        static void TestReference() 
        {
            Console.WriteLine("\n--------- 特殊的引用类型 string  判等-----------");
            string temp1 = new string(new char[] { 'L', 'c' });
            string temp2 = new string(new char[] { 'L', 'c' });
            Console.WriteLine($"temp1 == temp2 = {temp1 == temp2}");
            // 作为特殊的引用类型，只判断值不判断地址
            Console.WriteLine($"temp1.Equals(temp2) = {temp1.Equals(temp2)}");
            // 输出结果是true true  
            object temp3 = temp1;
            object temp4 = temp2;
            Console.WriteLine($"temp3 == temp4 = {temp3 == temp4}");
            // object （非string）的引用类型，值一样，咱不认，认地址，因为地址不一样，所以上面等于 false
            Console.WriteLine($"temp3.Equals(temp4) = {temp3.Equals(temp4)}");
            // equals 用于判定对象的值。
            // 输出结果false true
        }

        // 装箱 拆箱：
        // 箱 ：盒子 Box 
        // 装、拆：动作
        static void Box() 
        {

        }

    }

    class Person 
    {
        public string sex;
    }
}
